<!DOCTYPE html>
<html lang="en">

<!-- Head tag -->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="google-site-verification" content="xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="keyword"  content="">
    <link rel="shortcut icon" href="/img/logo.png">
    <!-- Place this tag in your head or just before your close body tag. -->
    <script async defer src="https://buttons.github.io/buttons.js"></script>
    <title>
        
          多线程—AQS - 宋正兵的博客 | zbsong Blog
        
    </title>

    <link rel="canonical" href="zbsong.top/article/多线程—AQS/">

    <!-- Bootstrap Core CSS -->
    <link rel="stylesheet" href="/css/bootstrap.min.css">

    <!-- Custom CSS --> 
    <link rel="stylesheet" href="/css/beantech.min.css">

    <link rel="stylesheet" href="/css/donate.css">
    
    <!-- Pygments Highlight CSS -->
    <link rel="stylesheet" href="/css/highlight.css">

    <link rel="stylesheet" href="/css/widget.css">

    <link rel="stylesheet" href="/css/rocket.css">

    <link rel="stylesheet" href="/css/signature.css">

    <link rel="stylesheet" href="/css/toc.css">

    <!-- Custom Fonts -->
    <!-- <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css"> -->
    <!-- Hux change font-awesome CDN to qiniu -->
    <link href="https://cdn.staticfile.org/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">


    <!-- Hux Delete, sad but pending in China
    <link href='http://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
    <link href='http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/
    css'>
    -->


    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->

    <!-- ga & ba script hoook -->
    <script></script>
</head>


<!-- hack iOS CSS :active style -->
<body ontouchstart="">
	<!-- Modified by Yu-Hsuan Yen -->
<!-- Post Header -->
<style type="text/css">
    header.intro-header{
        
            background-image: url('https://api.dujin.org/bing/1920.php')
            /*post*/
        
    }
    
</style>

<header class="intro-header" >
    <!-- Signature -->
    <div id="signature">
        <div class="container">
            <div class="row">
                <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                
                    <div class="post-heading">
                        <div class="tags">
                            
                              <a class="tag" href="/tags/#java" title="java">java</a>
                            
                        </div>
                        <h1>多线程—AQS</h1>
                        <!-- <h2 class="subheading">AQS</h2> -->
                        <span class="meta">
                            宋正兵 on
                            2021-03-22
                        </span>
                    </div>
                


                </div>
            </div>
        </div>
    </div>
</header>

    <!-- Navigation -->
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header page-scroll">
            <button type="button" class="navbar-toggle">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">songzblink</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <!-- Known Issue, found by Hux:
            <nav>'s height woule be hold on by its content.
            so, when navbar scale out, the <nav> will cover tags.
            also mask any touch event of tags, unfortunately.
        -->
        <div id="huxblog_navbar">
            <div class="navbar-collapse">
                <ul class="nav navbar-nav navbar-right">
                    <li>
                        <a href="/">主页</a>
                    </li>
					<li>
                        <a href="/archive/">归档</a>
                    </li>
					<li>
                        <a href="/tags/">标签</a>
                    </li>
					<li>
                        <a href="/about/">关于</a>
                    </li>
					<!--
					修改about在前面的问题
                    
                        
                    
                        
                        <li>
                            <a href="/about/">关于</a>
                        </li>
                        
                    
                        
                        <li>
                            <a href="/archive/">归档</a>
                        </li>
                        
                    
                        
                        <li>
                            <a href="/tags/">标签</a>
                        </li>
                        
                    
                    -->
                </ul>
            </div>
        </div>
        <!-- /.navbar-collapse -->
    </div>
    <!-- /.container -->
</nav>
<script>
    // Drop Bootstarp low-performance Navbar
    // Use customize navbar with high-quality material design animation
    // in high-perf jank-free CSS3 implementation
    var $body   = document.body;
	// querySelector 获取文档中 id="demo" 的元素
    var $toggle = document.querySelector('.navbar-toggle');
    var $navbar = document.querySelector('#huxblog_navbar');
    var $collapse = document.querySelector('.navbar-collapse');

    $toggle.addEventListener('click', handleMagic)
    function handleMagic(e){
        if ($navbar.className.indexOf('in') > 0) {
        // CLOSE
            $navbar.className = " ";
            // wait until animation end.
            setTimeout(function(){
                // prevent frequently toggle
                if($navbar.className.indexOf('in') < 0) {
                    $collapse.style.height = "0px"
                }
            },400)
        }else{
        // OPEN
            $collapse.style.height = "auto"
            $navbar.className += " in";
        }
    }
</script>


    <!-- Main Content -->
    <!-- Modify by Yu-Hsuan Yen -->

<!-- Post Content -->
<article>
    <div class="container">
        <div class="row">

            <!-- Post Container -->
            <div class="
                col-lg-8 col-lg-offset-2
                col-md-10 col-md-offset-1
                post-container">

				
                <p>部分图片和内容参考：</p>
<ul>
<li><a href="https://www.cnblogs.com/waterystone/p/4920797.html" target="_blank" rel="noopener">Java并发之AQS详解</a></li>
<li><a href="https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html" target="_blank" rel="noopener">从ReentrantLock的实现看AQS的原理及应用</a></li>
</ul>
<h2 id="aqs概述">AQS概述</h2>
<p>AQS（ AbstractQueuedSynchronizer，抽象的队列式的同步器），是阻塞式锁和相关的同步器工具的框架，许多同步类实现都依赖于它，如常用的 Lock、Semaphore、ReentrantLock等。</p>
<p>特点：</p>
<ol>
<li>
<p>用 volatile int state 属性来表示资源的状态（分独占模式和共享模式），独占模式（Exclusive）是只有一个线程能够访问资源（如 ReentrantLock），而共享模式（Share）可以允许多个线程访问资源（如 Semaphore / CountDownLatch）。</p>
<p>state 的访问方式有三种：</p>
<ol>
<li><code>getState()</code>——获取 state 状态</li>
<li><code>setState()</code>——设置 state 状态</li>
<li><code>compareAndSetState()</code>——CAS 机制设置 state 状态</li>
</ol>
</li>
<li>
<p>提供了一个 FIFO 的线程等待队列，类似于 Monitor 的 EntryList</p>
</li>
<li>
<p>条件变量来实现等待唤醒机制，支持多个条件变量，类似于 Monitor 的 WaitSet</p>
</li>
</ol>
<p><strong>自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可</strong>，至于具体线程等待队列的维护（如获取资源失败入队/唤醒出队等），AQS 已经在顶层实现好了。</p>
<p>自定义同步器实现时主要实现以下几种方法：</p>
<ul>
<li><code>tryAcquire(int)</code>：独占方式。尝试获取资源，成功返回 true，失败则返回 false。</li>
<li><code>tryRelease(int)</code>：独占方式。尝试释放资源，成功返回 true，失败则返回 false。</li>
<li><code>tryAcquireShared(int)</code>：共享方式。尝试获取资源。负数表示失败；0 表示成功，但没有剩余可用资源；正数表示成功，且有剩余资源。</li>
<li><code>tryReleaseShared(int)</code>：共享方式。尝试释放资源，如果释放后允许唤醒后续等待节点返回 true，否则返回 false。</li>
<li><code>isHeldExclusively()</code>：该线程是否正独占资源。只有用到 condition 才需要去实现它。</li>
</ul>
<h2 id="实现不可重入锁">实现不可重入锁</h2>
<p><strong>自定义同步器</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MySync</span> <span class="keyword">extends</span> <span class="title">AbstractQueuedSynchronizer</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 尝试获取锁</span></span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>)) &#123;</span><br><span class="line">            <span class="comment">// 锁已经加上了，并且设置owner为当前线程</span></span><br><span class="line">            setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">tryRelease</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 释放锁，如果能够执行到释放，那么证明一定是拿到了锁的，所以不用再进行CAS操作</span></span><br><span class="line">        setExclusiveOwnerThread(<span class="keyword">null</span>);</span><br><span class="line">        setState(<span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="comment">// 是否持有独占锁</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">isHeldExclusively</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> getState() == <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Condition <span class="title">newCondition</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ConditionObject();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>自定义锁</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyLock</span> <span class="keyword">implements</span> <span class="title">Lock</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 自定义同步器，独占锁</span></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">MySync</span> <span class="keyword">extends</span> <span class="title">AbstractQueuedSynchronizer</span> </span>&#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> MySync mySync = <span class="keyword">new</span> MySync();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="comment">// 加锁</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        mySync.acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="comment">// 加锁，可打断</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lockInterruptibly</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        mySync.acquireInterruptibly(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="comment">// 尝试加锁，只试一次</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">tryLock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> mySync.tryAcquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="comment">// 带超时加锁</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">tryLock</span><span class="params">(<span class="keyword">long</span> time, TimeUnit unit)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> mySync.tryAcquireNanos(<span class="number">1</span>, unit.toNanos(time));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="comment">// 解锁</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unlock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        mySync.release(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span> <span class="comment">// 创建条件变量</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Condition <span class="title">newCondition</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> mySync.newCondition();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>测试</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">MyLock lock = <span class="keyword">new</span> MyLock();</span><br><span class="line"><span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">    lock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        log.debug(<span class="string">"locking..."</span>);</span><br><span class="line">        Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        log.debug(<span class="string">"unlocking..."</span>);</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="string">"t1"</span>).start();</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">    lock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        log.debug(<span class="string">"locking..."</span>);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        log.debug(<span class="string">"unlocking..."</span>);</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="string">"t2"</span>).start();</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">20:06:11:472 [t1] c.Demo1 - locking...</span></span><br><span class="line"><span class="comment">20:06:13:480 [t1] c.Demo1 - unlocking...</span></span><br><span class="line"><span class="comment">20:06:13:480 [t2] c.Demo1 - locking...</span></span><br><span class="line"><span class="comment">20:06:13:480 [t2] c.Demo1 - unlocking...</span></span><br><span class="line"><span class="comment">**/</span></span><br></pre></td></tr></table></figure>
<h2 id="reentrantlock原理">ReentrantLock原理</h2>
<h3 id="reentrantlock特性">ReentrantLock特性</h3>
<p>ReentrantLock 意思为可重入锁，指的是一个线程能够对一个临界资源重复加锁。</p>
<p>它与 Synchronized 关键字的对比：</p>
<p><img src="https://p0.meituan.net/travelcube/412d294ff5535bbcddc0d979b2a339e6102264.png" alt="metuan"></p>
<p>伪代码的比较：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// **************************Synchronized的使用方式**************************</span></span><br><span class="line"><span class="comment">// 1.用于代码块</span></span><br><span class="line"><span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;&#125;</span><br><span class="line"><span class="comment">// 2.用于对象</span></span><br><span class="line"><span class="keyword">synchronized</span> (object) &#123;&#125;</span><br><span class="line"><span class="comment">// 3.用于方法</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">test</span> <span class="params">()</span> </span>&#123;&#125;</span><br><span class="line"><span class="comment">// 4.可重入</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">	<span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// **************************ReentrantLock的使用方式**************************</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span> <span class="params">()</span> throw Exception </span>&#123;</span><br><span class="line">	<span class="comment">// 1.初始化选择公平锁、非公平锁</span></span><br><span class="line">	ReentrantLock lock = <span class="keyword">new</span> ReentrantLock(<span class="keyword">true</span>);</span><br><span class="line">	<span class="comment">// 2.可用于代码块</span></span><br><span class="line">	lock.lock();</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="comment">// 3.支持多种加锁方式，比较灵活; 具有可重入特性</span></span><br><span class="line">			<span class="keyword">if</span>(lock.tryLock(<span class="number">100</span>, TimeUnit.MILLISECONDS))&#123; &#125;</span><br><span class="line">		&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">			<span class="comment">// 4.手动释放锁</span></span><br><span class="line">			lock.unlock()</span><br><span class="line">		&#125;</span><br><span class="line">	&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">		lock.unlock();</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="reentrantlock非公平锁实现原理">ReentrantLock非公平锁实现原理</h3>
<h4 id="加锁流程"><strong>加锁流程</strong></h4>
<p>构造器默认为非公平实现</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ReentrantLock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    sync = <span class="keyword">new</span> NonfairSync();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>没有竞争时：</p>
<ol>
<li>通过 CAS 尝试将 state 由 0 改为 1</li>
<li>成功后将 owner 改成当前线程（将当前线程设置为独占线程）</li>
</ol>
<p><img src="https://pic.tyzhang.top/images/2020/07/03/meiyoujingzheng.jpg" alt="1"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 没有竞争时候</span></span><br><span class="line">    <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">        setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="comment">// 竞争出现了，进入acquire方法进行后续处理</span></span><br><span class="line">        acquire(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>第一个竞争出现的时候，进入 acquire 方法进行后续处理</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/03/thread1confi.jpg" alt="2"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">        selfInterrupt();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 标记是否成功拿到资源</span></span><br><span class="line">    <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 标记等待过程中是否中断过</span></span><br><span class="line">        <span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;</span><br><span class="line">        <span class="comment">// 开始自旋，要么获取锁，要么中断</span></span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="comment">// 获取前驱节点，node表示当前线程Thread-1</span></span><br><span class="line">            <span class="keyword">final</span> Node p = node.predecessor();</span><br><span class="line">            <span class="comment">// 如果p是头结点，说明当前节点在真实数据队列的首部，就尝试获取锁（别忘了头结点是虚节点）</span></span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">                <span class="comment">// 获取锁成功，头指针移动到当前node</span></span><br><span class="line">                setHead(node);</span><br><span class="line">                p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="keyword">false</span>;</span><br><span class="line">                <span class="keyword">return</span> interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 说明p为头节点且当前没有获取到锁或者是p不为头结点</span></span><br><span class="line">            <span class="comment">// 判断当前node是否要被阻塞(被阻塞条件：前驱节点的waitStatus为-1)</span></span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt())</span><br><span class="line">                interrupted = <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>Thread-1 执行</p>
<ol>
<li>
<p>CAS 尝试将 state 由 0 改为 1，结果失败</p>
</li>
<li>
<p>进入 tryAcquire 逻辑，这时 state 已经是 1，结果仍然失败</p>
</li>
<li>
<p>接下来进入 addWaiter 逻辑，构造 Node 队列</p>
<ol>
<li>图中黄色三角表示该 Node 的 waitStatus 状态，其中 0 为默认正常状态</li>
<li>Node 的创建是懒惰的</li>
<li>其中第一个 Node 称为 Dummy（哑元）或哨兵，用来占位，并不关联线程</li>
</ol>
<blockquote>
<p>addWaiter 逻辑：把对应的线程以 Node 的数据结构形式加入到双端队列里，返回的是一个包含该线程的 Node。而这个 Node 会作为参数，进入到 acquireQueued 方法中。acquireQueued 方法可以对排队中的线程进行“获锁”操作。</p>
</blockquote>
</li>
</ol>
<p><img src="https://pic.tyzhang.top/images/2020/07/03/sadsdasada.jpg" alt="3"></p>
<p>当前线程进入 acquireQueued 逻辑</p>
<ol>
<li>acquireQueued 会在一个死循环中不断尝试获得锁，失败后进入 park 阻塞</li>
<li>如果当前线程所在的节点紧邻着 head（排在第二位），那么再次 tryAcquire 尝试获取锁，当然这时 state 仍然为 1，失败</li>
<li>进入 shouldParkAfterFailedAcquire 逻辑，将前驱 Node，即 head 的 waitStatus 改为 -1，表示他有责任唤醒它的后继节点（Thread-1 长期未获得锁应该进入阻塞，所以需要有一个节点去唤醒，由前驱唤醒），这次返回 false</li>
<li>shouldParkAfterFailedAcquire 逻辑执行完毕后再次进入循环，再次 tryAcquire 尝试获取锁，这时候 state 仍为1，失败</li>
<li>当再次进入 shouldParkAfterFailedAcquire 逻辑时候，这时候因为前驱 Node 的 waitStatus 已经是 -1，这次返回 true</li>
<li>进入 parkAndCheckInterupt，Thread-1 park，灰色表示</li>
</ol>
<blockquote>
<p>acquireQueued 逻辑：一个线程获取锁失败了，被放入等待队列，acquireQueued 会把放入队列中的线程不断去获取锁，直到获取成功或者不再需要获取（中断）。</p>
<p><img src="https://p0.meituan.net/travelcube/c124b76dcbefb9bdc778458064703d1135485.png" alt="meituan"></p>
</blockquote>
<p><img src="https://pic.tyzhang.top/images/2020/07/04/10f75e333a3e5cc05fabfe28ec4b4470.jpg" alt="4"></p>
<p>再次有多个线程经历上述竞争失败，会变成下图</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/04/91616ef59ceb9dc48e39270ccf43d625.jpg" alt="5"></p>
<h3 id="解锁流程">解锁流程</h3>
<p>Thread-0 释放锁，进入 tryRelease 流程，如果成功：</p>
<ul>
<li>设置 exclusiveOwnerThread 为 null</li>
<li>state = 0</li>
</ul>
<p><img src="https://pic.tyzhang.top/images/2020/07/04/80f368aeeae572a4efa6d145804e3103.jpg" alt="6"></p>
<p>当前队列不为 null，并且 head 的 waitStatus 为 -1，进入 unparkSuccessor 流程。找到队列中离 head 最近的一个没取消（cancelAcquire(node) 方法取消，相关细节省略，有兴趣的读者自己查阅美团的那篇博客有讲解）的 Node，unpark 恢复其运行。回到 Thread-1 的 acquireQueued 流程</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/04/1.jpg" alt="7"></p>
<p>如果没有竞争，加锁成功，会设置</p>
<ul>
<li>exclusiveOwnerThread 为 Thread-1，state = 1</li>
<li>head 指向刚刚 Thread-1 所在的Node，该 Node 清空 Thread 信息</li>
<li>原本的 head 因为从链表断开，而可被垃圾回收</li>
</ul>
<p>如果这时候有其他线程来竞争，如 Thread-4，又碰巧被 Thread-4 抢先</p>
<ul>
<li>Thread-4 被设置为 exclusiveOwnerThread，state = 1</li>
<li>Thread-1 再次进入 acquireQueued 流程，获取锁失败，重新进入 park 阻塞</li>
</ul>
<p><img src="https://pic.tyzhang.top/images/2020/07/04/546cfe8610245886c3e86f1cd56918ee.jpg" alt="8"></p>
<p>ReentrantLock 在解锁的时候，并不区分公平锁和非公平锁。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.ReentrantLock</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unlock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	sync.release(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到，释放锁的地方是通过框架来完成的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">release</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">	<span class="comment">// 下边自定义的tryRelease如果返回true，说明该锁没有被任何线程持有</span></span><br><span class="line">	<span class="keyword">if</span> (tryRelease(arg)) &#123;</span><br><span class="line">		<span class="comment">// 获取头结点</span></span><br><span class="line">		Node h = head;</span><br><span class="line">		<span class="comment">// 头结点不为空并且头结点的waitStatus不是初始化节点情况，解除线程挂起状态</span></span><br><span class="line">		<span class="keyword">if</span> (h != <span class="keyword">null</span> &amp;&amp; h.waitStatus != <span class="number">0</span>)</span><br><span class="line">			unparkSuccessor(h);</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在 ReentrantLock 中公平锁和非公平锁的父类 Sync 定义了可重入锁的释放机制。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.ReentrantLock.Sync</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法返回当前锁是不是没有被线程持有</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryRelease</span><span class="params">(<span class="keyword">int</span> releases)</span> </span>&#123;</span><br><span class="line">	<span class="comment">// 减少可重入次数</span></span><br><span class="line">	<span class="keyword">int</span> c = getState() - releases;</span><br><span class="line">	<span class="comment">// 当前线程不是持有锁的线程，抛出异常</span></span><br><span class="line">	<span class="keyword">if</span> (Thread.currentThread() != getExclusiveOwnerThread())</span><br><span class="line">		<span class="keyword">throw</span> <span class="keyword">new</span> IllegalMonitorStateException();</span><br><span class="line">	<span class="keyword">boolean</span> free = <span class="keyword">false</span>;</span><br><span class="line">	<span class="comment">// 如果持有线程全部释放，将当前独占锁持有线程设置为null，并更新state</span></span><br><span class="line">	<span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">		free = <span class="keyword">true</span>;</span><br><span class="line">		setExclusiveOwnerThread(<span class="keyword">null</span>);</span><br><span class="line">	&#125;</span><br><span class="line">	setState(c);</span><br><span class="line">	<span class="keyword">return</span> free;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>h == null Head还没初始化。初始情况下，head == null，第一个节点入队，Head会被初始化一个虚拟节点。所以说，这里如果还没来得及入队，就会出现head == null 的情况。</p>
<p>h != null &amp;&amp; waitStatus == 0 表明后继节点对应的线程仍在运行中，不需要唤醒。</p>
<p>h != null &amp;&amp; waitStatus &lt; 0 表明后继节点可能被阻塞了，需要唤醒。</p>
</blockquote>
<h3 id="锁重入原理">锁重入原理</h3>
<p>同步状态 state 用来控制整体可重入的情况。state 用 volatile 来修饰的，用于保证一定的可见性和有序性。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> state;</span><br></pre></td></tr></table></figure>
<p>state 这个字段主要的过程：</p>
<ol>
<li>state 初始化的时候为 0，表示没有任何线程持有锁</li>
<li>当有线程持有该锁时，值就会在原来的基础上 +1，同一个线程多次获得锁时就会多次 +1，这里就是可重入的概念</li>
<li>解锁也是对这个字段 -1，一直到 0，此线程对锁释放</li>
</ol>
<p>在ReentrantLock里面，不管是公平锁还是非公平锁，都有一段逻辑。</p>
<p>公平锁：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line">    <span class="keyword">int</span> c = getState();</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 先检查 AQS 队列中是否有前驱节点, 没有才去竞争</span></span><br><span class="line">        <span class="keyword">if</span> (!hasQueuedPredecessors() &amp;&amp;</span><br><span class="line">            compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">            setExclusiveOwnerThread(current);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">        <span class="comment">// 主要完成了state++的操作</span></span><br><span class="line">        <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"Maximum lock count exceeded"</span>);</span><br><span class="line">        setState(nextc);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>非公平锁：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">nonfairTryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line">    <span class="keyword">int</span> c = getState();</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 没有任何线程持有锁，直接去竞争锁</span></span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">            setExclusiveOwnerThread(current);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">        <span class="comment">// 主要完成了state++的操作</span></span><br><span class="line">        <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"Maximum lock count exceeded"</span>);</span><br><span class="line">        setState(nextc);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="公平锁和非公平锁实现原理的区别">公平锁和非公平锁实现原理的区别</h3>
<p>上方两段代码中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 公平锁</span></span><br><span class="line"><span class="comment">// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire</span></span><br><span class="line"><span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// 先检查 AQS 队列中是否有前驱节点, 没有才去竞争</span></span><br><span class="line">    <span class="keyword">if</span> (!hasQueuedPredecessors() &amp;&amp;</span><br><span class="line">        compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">        setExclusiveOwnerThread(current);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 非公平锁</span></span><br><span class="line"><span class="comment">// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire</span></span><br><span class="line"><span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// 没有任何线程持有锁，直接去竞争锁</span></span><br><span class="line">    <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">        setExclusiveOwnerThread(current);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="中断恢复后的执行流程">中断恢复后的执行流程</h3>
<p>当线程被唤醒后，会执行 <code>return Thread.interrupted();</code>，这个函数返回的是当前执行线程的中断状态，并清除中断状态。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">parkAndCheckInterrupt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">	<span class="keyword">return</span> Thread.interrupted();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后再次回到 acquireQueued 代码，当 parkAndCheckInterrupt 返回 True 或者 False 的时候，interrupted 的值不同，但都会执行下次循环。如果这个时候获取锁成功，就会把当前 interrupted 返回。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		<span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;</span><br><span class="line">		<span class="keyword">for</span> (;;) &#123;</span><br><span class="line">			<span class="keyword">final</span> Node p = node.predecessor();</span><br><span class="line">			<span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">				setHead(node);</span><br><span class="line">				p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line">				failed = <span class="keyword">false</span>;</span><br><span class="line">				<span class="keyword">return</span> interrupted;</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp; parkAndCheckInterrupt())</span><br><span class="line">				interrupted = <span class="keyword">true</span>;</span><br><span class="line">			&#125;</span><br><span class="line">	&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">		<span class="keyword">if</span> (failed)</span><br><span class="line">			cancelAcquire(node);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果 acquireQueued 为 True，就会执行 selfInterrupt 方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">        selfInterrupt();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// java.util.concurrent.locks.AbstractQueuedSynchronizer</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">selfInterrupt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	Thread.currentThread().interrupt();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>该方法其实是为了中断线程。但为什么获取了锁以后还要中断线程呢？这部分属于Java提供的协作式中断知识内容，感兴趣同学可以查阅一下。这里简单介绍一下：</p>
<ol>
<li>当中断线程被唤醒时，并不知道被唤醒的原因，可能是当前线程<strong>在等待中被中断</strong>，也可能是<strong>释放了锁以后被唤醒</strong>。因此我们通过 Thread.interrupted() 方法检查中断标记（该方法返回了当前线程的中断状态，并将当前线程的中断标识设置为 False），并记录下来，如果发现该线程被中断过，就再中断一次。</li>
<li>线程在等待资源的过程中被唤醒，唤醒后还是会不断地去尝试获取锁，直到抢到锁为止。也就是说，在整个流程中，并不响应中断，只是记录中断记录。最后抢到锁返回了，那么如果被中断过的话，就需要补充一次中断。</li>
</ol>
<h3 id="可打断模式">可打断模式</h3>
<p>不可打断模式，即使它被打断，仍会驻留在AQS队列中，一直要等到获得锁后方能得知自己被打断了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp; parkAndCheckInterrupt())</span><br><span class="line">    <span class="comment">// 如果是因为 interrupt 被唤醒, 返回打断状态为 true</span></span><br><span class="line">    interrupted = <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可打断模式</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp; parkAndCheckInterrupt()) &#123;</span><br><span class="line">    <span class="comment">// 在 park 过程中如果被 interrupt 会进入此</span></span><br><span class="line">    <span class="comment">// 这时候抛出异常, 而不会再次进入 for (;;)</span></span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> InterruptedException();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>两者函数不同。</p>
<h3 id="条件变量实现原理">条件变量实现原理</h3>
<p>每个条件变量其实就对应着一个等待队列，其实现类是 ConditionObject。</p>
<h4 id="await">await</h4>
<p>开始 Thread-0 持有锁，调用 await，进入 ConditionObject 的 addConditionWaiter 流程。创建新的 Node 状态为 -2(Node.CONDITION)，关联 Thread-0，加入等待队列尾部。</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/05/CONDITION.jpg" alt=""></p>
<p>接下来进入 AQS 的 fullyRelease 流程，释放同步器上的所有锁。</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/05/87c653dc618f6e7a0109f8ebf87d799f.jpg" alt=""></p>
<p>unpark AQS 队列中的下一个节点，竞争锁，假设没有其他竞争线程，那么 Thread-1 竞争成功。</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/05/e15c5b4236558cdb9596ed09dca7f9cf.jpg" alt=""></p>
<p>park 阻塞 Thread-0</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/05/Thread0.jpg" alt=""></p>
<h4 id="signal">signal</h4>
<p>以上图为例，接下来假设 Thread-1 去唤醒 Thread-0。进入 ConditionObject 的 doSignal 流程，取得等待队列中的第一个 Node，即 Thread-0 所在的 Node。</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/05/dosignal.jpg" alt=""></p>
<p>执行 transferForSignal 流程，将该 Node 加入到 AQS 尾部，将 Thread-0 的 waitStatus 改为 0，Thread-3 的 waitStatus 改为 -1。</p>
<p><img src="https://pic.tyzhang.top/images/2020/07/05/waitStatus.jpg" alt=""></p>
<p>Thread-1 释放锁，进入 Unlock 流程。</p>
<h2 id="读写锁">读写锁</h2>
<h3 id="reentrantreadwritelock">ReentrantReadWriteLock</h3>
<p>对于一个数据，不管是几个线程同时读都不会出现任何问题，但是写就不一定了，如果多个线程对同一个数据进行更改就可能会出现数据不一致的问题，此时容易想到的一个方法就是对数据加锁。</p>
<p>线程写数据的时候加锁能够确保数据的准确性，但是读数据的时候再加锁就会大大降低效率，这时可以采用把读数据和写数据分开，加上两把不同的锁，不仅能保证正确性，还能提高效率。</p>
<p><strong>特性：</strong></p>
<ol>
<li>可降级：线程获取写锁后可以获取读锁，然后释放写入锁，这样就从写锁变成了读锁，从而实现锁降级的特性。</li>
<li>不可升级：线程获取读锁后不能直接升级为写锁。需要释放所有读锁，才能获取写锁。</li>
<li>读锁不支持条件变量</li>
</ol>
<p><strong>读锁的意义：</strong> 可以从不可升级的特性看出来，防止在读数据的时候，其他线程更改了数据，导致读取出来的数据过时。</p>
<p>读写锁可以应用到缓存上，去保证缓存和数据库的一致性。</p>
<p>当读操作远远高于写操作时，使用<strong>读写锁</strong>让<strong>读读</strong>可以并发，提高性能。类似于数据库中的 <code>select ... from ... lock in share mode</code>。</p>
<blockquote>
<p>总结：读读可以并发，读写、写写是互斥的。</p>
<p>在AQS加锁的过程当中，写锁是独占锁，读锁是共享锁。</p>
<p>可降级：写锁获取锁后，会设置 owner 线程为当前线程，后面试图获取锁的线程会加入到等待队列中。（写锁降级流程）如果紧邻 head 节点的第二个节点是想要获取读锁（共享锁）的线程，那么就会唤醒该线程，在共享锁的代码流程中还会接着去判断下一个节点是否为共享锁，如果是则继续唤醒，如果不是则结束当前流程；如果紧邻 head 节点的第二个节点是想要获取写锁（独占锁）的线程，那么按照 ReentrantLock 的唤醒流程来走。</p>
<p>不可升级：读锁获取锁后，只会修改 state 的状态，而不会去设置 owner 线程，之后如果有线程想要获取写锁，当通过 state 状态发现已经有（读）锁的时候，加锁失败，进入等待队列等候唤醒。</p>
<p>具体的分析可以查阅相关源码。</p>
</blockquote>
<p>提供一个<strong>数据容器类</strong>内部分别使用读锁保护数据的 read() 方法，写锁保护数据的 write() 方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Container</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Object data;</span><br><span class="line">    <span class="keyword">private</span> ReentrantReadWriteLock rw = <span class="keyword">new</span> ReentrantReadWriteLock();</span><br><span class="line">    <span class="keyword">private</span> ReentrantReadWriteLock.ReadLock r = rw.readLock();</span><br><span class="line">    <span class="keyword">private</span> ReentrantReadWriteLock.WriteLock w = rw.writeLock();</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">read</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        log.debug(<span class="string">"获取读锁。。。"</span>);</span><br><span class="line">        r.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            log.debug(<span class="string">"读取"</span>);</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">            <span class="keyword">return</span> data;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            log.debug(<span class="string">"释放读锁。。。"</span>);</span><br><span class="line">            r.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">write</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        log.debug(<span class="string">"获取写锁。。。"</span>);</span><br><span class="line">        w.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            log.debug(<span class="string">"写入"</span>);</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            log.debug(<span class="string">"释放写锁"</span>);</span><br><span class="line">            w.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    Container dataContainer = <span class="keyword">new</span> Container();</span><br><span class="line">    <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">        dataContainer.read();</span><br><span class="line">    &#125;, <span class="string">"t1"</span>).start();</span><br><span class="line">    <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">        dataContainer.read();</span><br><span class="line">    &#125;, <span class="string">"t2"</span>).start();</span><br><span class="line">    <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">        dataContainer.write();</span><br><span class="line">    &#125;, <span class="string">"t3"</span>).start();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">其中一种情况，可以看到写锁和读锁是互斥的</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:56.489 [t1] DEBUG Container - 获取读锁。。。</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:56.489 [t2] DEBUG Container - 获取读锁。。。</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:56.489 [t3] DEBUG Container - 获取写锁。。。</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:56.492 [t1] DEBUG Container - 读取</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:56.492 [t2] DEBUG Container - 读取</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:57.492 [t1] DEBUG Container - 释放读锁。。。</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:57.492 [t2] DEBUG Container - 释放读锁。。。</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:57.492 [t3] DEBUG Container - 写入</span></span><br><span class="line"><span class="comment">2020-07-05 15:36:58.493 [t3] DEBUG Container - 释放写锁</span></span><br><span class="line"><span class="comment">**/</span></span><br></pre></td></tr></table></figure>
<p><strong>注意事项</strong></p>
<ul>
<li>
<p>读锁不支持条件变量</p>
</li>
<li>
<p>重入时升级不支持：即持有读锁的情况下去获取写锁，会导致获取写锁永久等待</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">r.lock();</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    w.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125; <span class="keyword">finally</span>&#123;</span><br><span class="line">        w.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125; <span class="keyword">finally</span>&#123;</span><br><span class="line">    r.unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li>
<p>重入时降级支持：即持有写锁的情况下去获取读锁</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CachedData</span> </span>&#123;</span><br><span class="line">    Object data;</span><br><span class="line">    <span class="comment">// 是否有效，如果失效，需要重新计算 data</span></span><br><span class="line">    <span class="keyword">volatile</span> <span class="keyword">boolean</span> cacheValid;</span><br><span class="line">    <span class="keyword">final</span> ReentrantReadWriteLock rwl = <span class="keyword">new</span> ReentrantReadWriteLock();</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">processCachedData</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        rwl.readLock().lock();</span><br><span class="line">        <span class="keyword">if</span> (!cacheValid) &#123;</span><br><span class="line">            <span class="comment">// 获取写锁前必须释放读锁</span></span><br><span class="line">            rwl.readLock().unlock();</span><br><span class="line">            rwl.writeLock().lock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新</span></span><br><span class="line">                <span class="keyword">if</span> (!cacheValid) &#123;</span><br><span class="line">                    data = ...</span><br><span class="line">                        cacheValid = <span class="keyword">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存</span></span><br><span class="line">                rwl.readLock().lock();</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                rwl.writeLock().unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 自己用完数据, 释放读锁</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            use(data);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            rwl.readLock().unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h3 id="stampedlock">StampedLock</h3>
<p>该类自 JDK 8 加入，是为了进一步优化读性能，它的特点是在使用读锁、写锁时候都必须配合【戳】的使用。</p>
<p>加解读锁</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">long</span> stamp = lock.readLock();</span><br><span class="line">lock.unlockRead(stamp);</span><br></pre></td></tr></table></figure>
<p>加解写锁</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">long</span> stamp = lock.writeLock();</span><br><span class="line">lock.unlockWrite(stamp);</span><br></pre></td></tr></table></figure>
<p>乐观读，StampedLock 支持 <code>tryOptimisticRead()</code> 方法(乐观读)，读取完毕之后需要做一次戳校验，如果校验通过，表示这期间确实没有写操作，数据可以安全使用，如果校验没通过，需要重新获取读锁，保证数据安全。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">long</span> stamp = lock.tryOptimisticRead();</span><br><span class="line"><span class="comment">// 验戳</span></span><br><span class="line"><span class="keyword">if</span>(!lock.validate(stamp))&#123;</span><br><span class="line">    <span class="comment">// 校验未通过，进行锁升级</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>提供一个数据容器类，内部分别使用读锁保护数据的 read() 方法，写锁保护数据的 write() 方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        ContainerStamped dataContainer = <span class="keyword">new</span> ContainerStamped(<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            dataContainer.read(<span class="number">1</span>);</span><br><span class="line">        &#125;, <span class="string">"t1"</span>).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            dataContainer.write(<span class="number">1000</span>);</span><br><span class="line">        &#125;, <span class="string">"t2"</span>).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ContainerStamped</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> data;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> StampedLock lock = <span class="keyword">new</span> StampedLock();</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">ContainerStamped</span><span class="params">(<span class="keyword">int</span> data)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.data = data;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">read</span><span class="params">(<span class="keyword">int</span> readTime)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> stamp = lock.tryOptimisticRead();</span><br><span class="line">        log.debug(<span class="string">"乐观锁&#123;&#125;"</span>, stamp);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(readTime);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (lock.validate(stamp)) &#123;</span><br><span class="line">            log.debug(<span class="string">"验戳&#123;&#125;"</span>, stamp);</span><br><span class="line">        &#125;</span><br><span class="line">        log.debug(<span class="string">"锁升级&#123;&#125;"</span>, stamp);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            stamp = lock.readLock();</span><br><span class="line">            log.debug(<span class="string">"读锁&#123;&#125;"</span>, stamp);</span><br><span class="line">            Thread.sleep(readTime);</span><br><span class="line">            log.debug(<span class="string">"读锁操作完成&#123;&#125;"</span>, stamp);</span><br><span class="line">            <span class="keyword">return</span> data;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            log.debug(<span class="string">"读解锁&#123;&#125;"</span>, stamp);</span><br><span class="line">            lock.unlockRead(stamp);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">write</span><span class="params">(<span class="keyword">int</span> newData)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> stamp = lock.writeLock();</span><br><span class="line">        log.debug(<span class="string">"写锁&#123;&#125;"</span>, stamp);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">            <span class="keyword">this</span>.data = newData;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            log.debug(<span class="string">"写解锁&#123;&#125;"</span>, stamp);</span><br><span class="line">            lock.unlockWrite(stamp);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">2020-07-05 21:06:38.482 [t1] DEBUG ContainerStamped - 乐观锁256</span></span><br><span class="line"><span class="comment">2020-07-05 21:06:38.482 [t2] DEBUG ContainerStamped - 写锁384</span></span><br><span class="line"><span class="comment">2020-07-05 21:06:38.490 [t1] DEBUG ContainerStamped - 验戳失败256	// 锁升级，但是被写锁阻塞了</span></span><br><span class="line"><span class="comment">2020-07-05 21:06:40.488 [t2] DEBUG ContainerStamped - 写解锁384</span></span><br><span class="line"><span class="comment">2020-07-05 21:06:40.489 [t1] DEBUG ContainerStamped - 读锁513	// 等写锁释放之后才能加上</span></span><br><span class="line"><span class="comment">2020-07-05 21:06:40.491 [t1] DEBUG ContainerStamped - 读锁操作完成513</span></span><br><span class="line"><span class="comment">2020-07-05 21:06:40.491 [t1] DEBUG ContainerStamped - 读解锁513</span></span><br><span class="line"><span class="comment">**/</span></span><br></pre></td></tr></table></figure>
<p><strong>注意</strong></p>
<ul>
<li>StampedLock 不支持条件变量</li>
<li>不支持可重入</li>
</ul>
<h2 id="semaphore">Semaphore</h2>
<p>信号量，用来限制能同时访问共享资源的线程上限。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        Semaphore semaphore = <span class="keyword">new</span> Semaphore(<span class="number">2</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">7</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    semaphore.acquire();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    log.debug(<span class="string">"running.."</span>);</span><br><span class="line">                    Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                    log.debug(<span class="string">"end..."</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    semaphore.release();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:23.844 [Thread-2] DEBUG Test - running..</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:23.844 [Thread-1] DEBUG Test - running..</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:24.847 [Thread-1] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:24.847 [Thread-2] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:24.847 [Thread-3] DEBUG Test - running..</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:24.847 [Thread-4] DEBUG Test - running..</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:25.848 [Thread-3] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:25.848 [Thread-4] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:25.848 [Thread-6] DEBUG Test - running..</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:25.848 [Thread-5] DEBUG Test - running..</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:26.848 [Thread-6] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:26.848 [Thread-5] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:26.848 [Thread-7] DEBUG Test - running..</span></span><br><span class="line"><span class="comment">2020-07-05 21:19:27.848 [Thread-7] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">**/</span></span><br></pre></td></tr></table></figure>
<ul>
<li>使用 semaphore 限流。在访问高峰期时，让请求线程阻塞，高峰期过去之后再释放许可，当然它只适合限制单机数量，并且仅是限制线程数，而不是限制资源数。</li>
<li>用 semaphore 实现简单连接池，对比享元模式用 wait/notify 实现，性能和可读性显然更好，注意下面实现中线程数和数据库连接数是相等的。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Pool</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> poolSize;</span><br><span class="line">    <span class="keyword">private</span> Connection[] connections;</span><br><span class="line">    <span class="keyword">private</span> AtomicIntegerArray states;</span><br><span class="line">    <span class="keyword">private</span> Semaphore semaphore;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Pool</span><span class="params">(<span class="keyword">int</span> poolSize)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.poolSize = poolSize;</span><br><span class="line">        <span class="comment">// 许可数和资源数一致</span></span><br><span class="line">        <span class="keyword">this</span>.semaphore = <span class="keyword">new</span> Semaphore(poolSize);</span><br><span class="line">        <span class="keyword">this</span>.states = <span class="keyword">new</span> AtomicIntegerArray(<span class="keyword">new</span> <span class="keyword">int</span>[poolSize]);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; poolSize; i++) &#123;</span><br><span class="line">            connections[i] = <span class="keyword">new</span> DBConect(<span class="string">"连接"</span> + (i + <span class="number">1</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Connection <span class="title">borrow</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 获取许可</span></span><br><span class="line">            semaphore.acquire();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; poolSize; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (states.get(i) == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (states.compareAndSet(i, <span class="number">0</span>, <span class="number">1</span>)) &#123;</span><br><span class="line">                    log.debug(<span class="string">"borrow&#123;&#125;"</span>, connections[i]);</span><br><span class="line">                    <span class="keyword">return</span> connections[i];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 实际不会执行到这里，因为如果没有空闲连接，一定不会获取许可</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">free</span><span class="params">(Connection connection)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; poolSize; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (connections[i] == connection) &#123;</span><br><span class="line">                states.set(i, <span class="number">0</span>);</span><br><span class="line">                log.debug(<span class="string">"free&#123;&#125;"</span>, connection);</span><br><span class="line">                <span class="comment">// 归还许可</span></span><br><span class="line">                semaphore.release();</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>假设刚开始，permits(state) 为 3，这时候有五个线程来获取资源。假设其中 Thread-1，Thread-2，Thread-4 CAS 竞争成功，而 Thread-0 和 Thread-3 竞争失败，进入 AQS 队列 park  阻塞。这时 Thread-4 释放了 permits，接下来 Thread-0 竞争成功，permits 再次设置为 0，设置自己为 head 节点，断开原来的 head 节点，unpark 接下来的 Thread-3 节点，但由于 permits 是 0，因此 Thread-3 在尝试不成功后再次进入park状态。</p>
<h2 id="countdownlatch">CountDownLatch</h2>
<p>CountDownLatch 在多线程并发编程中充当一个计时器的功能，并且维护一个 count 的变量，并且其操作都是原子操作，该类主要通过 countDown() 和 await() 两个方法实现功能的，首先通过建立 CountDownLatch 对象，并且传入参数即为 count 初始值。如果一个线程调用了 await() 方法，那么这个线程便进入阻塞状态，并进入阻塞队列。如果一个线程调用了 countDown() 方法，则会使 count-1；当 count 的值为 0 时，这时候阻塞队列中调用 await() 方法的线程便会逐个被唤醒，从而进入后续的操作。</p>
<p>Thread 对象的 join 方法可以实现相同的功能，但是特别地，当使用了线程池时，则 join() 方法便无法实现。但 CountDownLatch 依然可以实现功能。</p>
<p>CountDownLatch 类主要使用的场景有明显的顺序要求：比如所有英雄都加载完之后才能进图游戏等等，因此 CountDownLatch 完善的是某种逻辑上的功能，使得线程按照正确的逻辑进行。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        CountDownLatch countDownLatch = <span class="keyword">new</span> CountDownLatch(<span class="number">3</span>);</span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            log.debug(<span class="string">"start..."</span>);</span><br><span class="line">            sleep(<span class="number">1</span>);</span><br><span class="line">            countDownLatch.countDown();</span><br><span class="line">            log.debug(<span class="string">"end..."</span>);</span><br><span class="line">        &#125;).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            log.debug(<span class="string">"start..."</span>);</span><br><span class="line">            sleep(<span class="number">2</span>);</span><br><span class="line">            countDownLatch.countDown();</span><br><span class="line">            log.debug(<span class="string">"end..."</span>);</span><br><span class="line">        &#125;).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(() -&gt; &#123;</span><br><span class="line">            log.debug(<span class="string">"start..."</span>);</span><br><span class="line">            sleep(<span class="number">3</span>);</span><br><span class="line">            countDownLatch.countDown();</span><br><span class="line">            log.debug(<span class="string">"end..."</span>);</span><br><span class="line">        &#125;).start();</span><br><span class="line">        log.debug(<span class="string">"main thread start,,,"</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            countDownLatch.await();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        log.debug(<span class="string">"main thread end,,,"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sleep</span><span class="params">(<span class="keyword">int</span> timeout)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(timeout * <span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">2020-07-06 19:46:06.721 [Thread-3] DEBUG Test - start...</span></span><br><span class="line"><span class="comment">2020-07-06 19:46:06.721 [Thread-2] DEBUG Test - start...</span></span><br><span class="line"><span class="comment">2020-07-06 19:46:06.721 [main] DEBUG Test - main thread start,,,</span></span><br><span class="line"><span class="comment">2020-07-06 19:46:06.721 [Thread-1] DEBUG Test - start...</span></span><br><span class="line"><span class="comment">2020-07-06 19:46:07.726 [Thread-1] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-06 19:46:08.727 [Thread-2] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-06 19:46:09.725 [Thread-3] DEBUG Test - end...</span></span><br><span class="line"><span class="comment">2020-07-06 19:46:09.725 [main] DEBUG Test - main thread end,,,</span></span><br><span class="line"><span class="comment">**/</span></span><br></pre></td></tr></table></figure>
<p>线程池改进：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">2</span>);</span><br><span class="line">ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">3</span>);</span><br><span class="line">executorService.submit(() -&gt; &#123;</span><br><span class="line">    log.debug(<span class="string">"begin..."</span>);</span><br><span class="line">    sleep(<span class="number">1</span>);</span><br><span class="line">    latch.countDown();</span><br><span class="line">    log.debug(<span class="string">"end...&#123;&#125;"</span>, latch.getCount());</span><br><span class="line">&#125;);</span><br><span class="line">executorService.submit(() -&gt; &#123;</span><br><span class="line">    log.debug(<span class="string">"begin..."</span>);</span><br><span class="line">    sleep(<span class="number">2</span>);</span><br><span class="line">    latch.countDown();</span><br><span class="line">    log.debug(<span class="string">"end...&#123;&#125;"</span>, latch.getCount());</span><br><span class="line">&#125;);</span><br><span class="line">executorService.submit(() -&gt; &#123;</span><br><span class="line">    log.debug(<span class="string">"waiting..."</span>);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        latch.await();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">    log.debug(<span class="string">"waiting end...&#123;&#125;"</span>, latch.getCount());</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>应用，等玩家都加载完毕后开启游戏：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">10</span>);</span><br><span class="line">    ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line">    Random r = <span class="keyword">new</span> Random();</span><br><span class="line">    String[] all = <span class="keyword">new</span> String[<span class="number">10</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">        <span class="keyword">int</span> k = i;</span><br><span class="line">        executorService.submit(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt;= <span class="number">100</span>; j++) &#123;</span><br><span class="line">                sleep(r.nextInt(<span class="number">100</span>));</span><br><span class="line">                all[k] = j + <span class="string">"%"</span>;</span><br><span class="line">                <span class="comment">// 不换行，“\r”后面的打印结果（覆盖）替换原来位置的打印结果</span></span><br><span class="line">                System.out.print(<span class="string">"\r"</span> + Arrays.toString(all));</span><br><span class="line">            &#125;</span><br><span class="line">            latch.countDown();</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    latch.await();</span><br><span class="line">    System.out.println(<span class="string">"\n游戏开始"</span>);</span><br><span class="line">    executorService.shutdown();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">[100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%]</span></span><br><span class="line"><span class="comment">游戏开始</span></span><br><span class="line"><span class="comment">**/</span></span><br></pre></td></tr></table></figure>
<p>微服务中等待多个服务器返回的结果：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">3</span>);</span><br><span class="line">    ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">3</span>);</span><br><span class="line">    log.debug(<span class="string">"start"</span>);</span><br><span class="line">    executorService.submit(() -&gt; &#123;</span><br><span class="line">        log.debug(<span class="string">"等待&#123;&#125;"</span>, method1());</span><br><span class="line">        latch.countDown();</span><br><span class="line">    &#125;);</span><br><span class="line">    executorService.submit(() -&gt; &#123;</span><br><span class="line">        log.debug(<span class="string">"等待&#123;&#125;"</span>, method2());</span><br><span class="line">        latch.countDown();</span><br><span class="line">    &#125;);</span><br><span class="line">    executorService.submit(() -&gt; &#123;</span><br><span class="line">        log.debug(<span class="string">"等待&#123;&#125;"</span>, method3());</span><br><span class="line">        latch.countDown();</span><br><span class="line">    &#125;);</span><br><span class="line">    latch.await();</span><br><span class="line">    System.out.println(<span class="string">"end"</span>);</span><br><span class="line">    executorService.shutdown();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 假设有三个方法分别从其他服务器获取数据</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">method1</span><span class="params">()</span> </span>&#123;<span class="keyword">return</span> sleep(<span class="number">1</span>);&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">method2</span><span class="params">()</span> </span>&#123;<span class="keyword">return</span> sleep(<span class="number">3</span>);&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">method3</span><span class="params">()</span> </span>&#123;<span class="keyword">return</span> sleep(<span class="number">5</span>);&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">2020-07-06 20:42:08.877 [main] DEBUG Test - start</span></span><br><span class="line"><span class="comment">2020-07-06 20:42:09.963 [pool-2-thread-1] DEBUG Test - 等待1</span></span><br><span class="line"><span class="comment">2020-07-06 20:42:11.965 [pool-2-thread-2] DEBUG Test - 等待3</span></span><br><span class="line"><span class="comment">2020-07-06 20:42:13.965 [pool-2-thread-3] DEBUG Test - 等待5</span></span><br><span class="line"><span class="comment">end</span></span><br><span class="line"><span class="comment">**/</span></span><br><span class="line"><span class="comment">// 将数据返回到主线程</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException, ExecutionException </span>&#123;</span><br><span class="line">    CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">3</span>);</span><br><span class="line">    ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">3</span>);</span><br><span class="line">    log.debug(<span class="string">"start"</span>);</span><br><span class="line">    Future&lt;Integer&gt; f1 = executorService.submit(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> method1();</span><br><span class="line">    &#125;);</span><br><span class="line">    Future&lt;Integer&gt; f2 = executorService.submit(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> method2();</span><br><span class="line">    &#125;);</span><br><span class="line">    Future&lt;Integer&gt; f3 = executorService.submit(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> method3();</span><br><span class="line">    &#125;);</span><br><span class="line">    System.out.println(<span class="string">"结果"</span> + f1.get());</span><br><span class="line">    System.out.println(<span class="string">"结果"</span> + f2.get());</span><br><span class="line">    System.out.println(<span class="string">"结果"</span> + f3.get());</span><br><span class="line">    System.out.println(<span class="string">"end"</span>);</span><br><span class="line">    executorService.shutdown();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="cyclicbarrier">CyclicBarrier</h2>
<p>循环栅栏，用来进行线程协作，等待线程满足某个个数。构造时候设置计数个数，每个线程运行到某个需要同步的时刻，调用 await 进行等待，当等待线程数满足计数个数时候，继续运行。用来解决 CountDownlatch 不能重用的问题，比如某几个同步线程需要运行三遍。CyclicBarrier 与 CountDownLatch 的主要区别在于 CyclicBarrier 是可以重用的 CyclicBarrier 可以被比喻为『人满发车』。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化数量和计数个数要一致</span></span><br><span class="line">CyclicBarrier cyclicBarrier = <span class="keyword">new</span> CyclicBarrier(<span class="number">2</span>, () -&gt; &#123;</span><br><span class="line">    System.out.println(<span class="string">"中断点..."</span>);</span><br><span class="line">&#125;);</span><br><span class="line">ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">2</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">2</span>; i++) &#123;</span><br><span class="line">    executorService.submit(() -&gt; &#123;</span><br><span class="line">        System.out.println(<span class="string">"线程1开始..."</span> + <span class="keyword">new</span> Date());</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            cyclicBarrier.await();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException | BrokenBarrierException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">"线程1继续向下运行..."</span> + <span class="keyword">new</span> Date());</span><br><span class="line">    &#125;);</span><br><span class="line">    executorService.submit(() -&gt; &#123;</span><br><span class="line">        System.out.println(<span class="string">"线程2开始..."</span> + <span class="keyword">new</span> Date());</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">            cyclicBarrier.await();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException | BrokenBarrierException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">"线程2继续向下运行..."</span> + <span class="keyword">new</span> Date());</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">线程1开始...Mon Jul 06 21:13:42 CST 2020</span></span><br><span class="line"><span class="comment">线程2开始...Mon Jul 06 21:13:42 CST 2020</span></span><br><span class="line"><span class="comment">中断点...</span></span><br><span class="line"><span class="comment">线程2继续向下运行...Mon Jul 06 21:13:43 CST 2020</span></span><br><span class="line"><span class="comment">线程1继续向下运行...Mon Jul 06 21:13:43 CST 2020</span></span><br><span class="line"><span class="comment">线程1开始...Mon Jul 06 21:13:43 CST 2020</span></span><br><span class="line"><span class="comment">线程2开始...Mon Jul 06 21:13:43 CST 2020</span></span><br><span class="line"><span class="comment">中断点...</span></span><br><span class="line"><span class="comment">线程1继续向下运行...Mon Jul 06 21:13:44 CST 2020</span></span><br><span class="line"><span class="comment">线程2继续向下运行...Mon Jul 06 21:13:44 CST 2020</span></span><br><span class="line"><span class="comment">**/</span></span><br></pre></td></tr></table></figure>
<h2 id="线程安全集合类">线程安全集合类</h2>
<ul>
<li>
<p>遗留的安全集合：</p>
<ul>
<li>Hashtable</li>
<li>Vector</li>
</ul>
</li>
<li>
<p>修饰的安全集合（使用 Collection 的方法用 synchronized 修饰）：</p>
<ul>
<li>Collections.synchronizedCollection</li>
<li>Collections.synchronizedList</li>
<li>Collections.synchronizedMap</li>
<li>Collections.synchronizedSet</li>
<li>Collections.synchronizedNavigableMap</li>
<li>Collections.synchronizedNavigableSet</li>
<li>Collections.synchronizedSortedMap</li>
<li>Collections.synchronizedSortedSet</li>
</ul>
</li>
<li>
<p>J.U.C 安全集合：</p>
<ul>
<li>
<p>它们有规律，里面包含三类关键词：Blocking、CopyOnWrite、Concurrent</p>
</li>
<li>
<p>Blocking 大部分实现基于锁，并提供用来阻塞的方法</p>
</li>
<li>
<p>CopyOnWrite 之类容器修改开销相对较重，修改时是利用拷贝式的方式来避免多线程时的线程安全问题，适用于读多写少的场景</p>
</li>
<li>
<p>Concurrent 类型的容器：</p>
<ul>
<li>内部很多操作使用 CAS 优化，一般可以提供较高吞吐量</li>
<li>弱一致性
<ul>
<li>遍历是弱一致性，例如，当利用迭代器遍历时，如果容器发生修改，迭代器仍然可以继续进行遍历，这时内容是旧的</li>
<li>求大小弱一致性，size 操作未必是 100% 准确</li>
<li>读取弱一致性</li>
</ul>
</li>
</ul>
<blockquote>
<p>遍历时如果发生了修改，对于<strong>非安全容器</strong>来讲，使用 fail-fast 机制也就是让遍历立刻失败，抛出<br>
<em>ConcurrentModificationException，不再继续遍历</em></p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h2 id="concurrenthashmap">ConcurrentHashMap</h2>
<p>ConcurrentHashMap 从 JDK 1.5 开始随 java.util.concurrent 包一起引入 JDK 中，在 JDK 8 以前，ConcurrentHashMap 都是基于 Segment 分段锁来实现的，在 JDK 8 以后，就换成 <strong>synchronized 和 CAS</strong> 这套实现机制了。</p>
<p>JDK 1.7 中维护一个 segment 数组，每个 segment 对应一把锁，默认大小为 16，初始化之后不能更改。</p>
<blockquote>
<p>在扩容到了一定程度之后，同时只能有 segment 个数的线程运行，限制了并发度，而对于 JDK 1.8 之后就优化了这个问题，是对桶的头节点进行加锁。</p>
</blockquote>
<p>JDK 1.8 中的 ConcurrentHashMap 不再使用 Segment 分段锁，而是以 table 数组的头结点作为 synchronized 的锁。和 JDK 1.8 中的 HashMap 类似，对于 hashCode 相同的时候，在 Node 节点的数量少于 8 个时，这时的 Node 存储结构是链表形式，时间复杂度为 O(N)，当 Node 节点的个数超过 8 个时，则会转换为红黑树，此时访问的时间复杂度为 O(long(N))。<strong>旧版本的一个 segment 锁，保护了多个 hash 桶，而JDK 8 版本的一个锁只保护一个 hash 桶</strong>，由于锁的粒度变小了，写操作的并发性得到了极大的提升。</p>
<h3 id="如何保证线程安全">如何保证线程安全</h3>
<ul>
<li>使用 volatile 保证当 Node 中的值变化时对于其他线程是可见的</li>
<li>使用 table 数组的头结点作为 synchronized 的锁来保证写操作的安全</li>
<li>当头结点为 null 时，使用 CAS 操作来保证数据能正确的写入。</li>
</ul>
<h3 id="高效扩容">高效扩容</h3>
<ul>
<li>
<p>扩容线程增大</p>
<p>扩容时，需要锁的保护。因此：<strong>旧版本最多可以同时扩容的线程数是 segment 锁的个数</strong>。<br>
而 JDK 8 的版本，理论上最多可以同时扩容的线程数是：hash 桶的个数（table 数组的长度）。但是为了防止扩容线程过多，ConcurrentHashMap 规定了扩容线程每次最少迁移 16 个 hash 桶，因此 <strong>JDK 8 的版本实际上最多可以同时扩容的线程数是：hash 桶的个数 / 16</strong>。</p>
</li>
<li>
<p>扩容期间，依然保证较高的并发度</p>
<p>旧版本的 segment 锁，锁定范围太大，导致扩容期间，写并发度，严重下降。而新版本的采用更加细粒度的 hash 桶级别锁，扩容期间，依然可以保证写操作的并发度。</p>
</li>
</ul>
<h3 id="concurrenthashmap的put方法是如何通过cas确保线程安全的">ConcurrentHashMap的put方法是如何通过CAS确保线程安全的</h3>
<p>假设此时有 2 个 put 线程，都发现此时桶为空，Thread-1 执行 casTabAt(tab,i,null,node1)，此时 tab[i] 等于预期值 null，因此会插入 node1。随后 Thread-2 执行 casTabAt(tba,i,null,node2)，此时 tab[i] 不等于预期值 null，插入失败。然后Thread-2 会回到 for 循环开始处，重新获取 tab[i] 作为预期值，重复上述逻辑。</p>
<p>以上通过 for 循环 + CAS 操作，实现并发安全的方式就是无锁算法（lock free)的经典实现。</p>
<h3 id="重要属性和内部类">重要属性和内部类</h3>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 默认为 0</span></span><br><span class="line"><span class="comment">// 当初始化时, 为 -1</span></span><br><span class="line"><span class="comment">// 当扩容时, 为 -(1 + 扩容线程数)</span></span><br><span class="line"><span class="comment">// 当初始化或扩容完成后，为 下一次的扩容的阈值大小</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> <span class="keyword">int</span> sizeCtl;</span><br><span class="line"><span class="comment">// 整个 ConcurrentHashMap 就是一个 Node[]</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">implements</span> <span class="title">Map</span>.<span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;&#125;</span><br><span class="line"><span class="comment">// hash 表</span></span><br><span class="line"><span class="keyword">transient</span> <span class="keyword">volatile</span> Node&lt;K,V&gt;[] table;</span><br><span class="line"><span class="comment">// 扩容时的 新 hash 表</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node&lt;K,V&gt;[] nextTable;</span><br><span class="line"><span class="comment">// 扩容时如果某个 bin 迁移完毕, 用 ForwardingNode 作为旧 table bin 的头结点</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ForwardingNode</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;&#125;</span><br><span class="line"><span class="comment">// 用在 compute 以及 computeIfAbsent 时, 用来占位, 计算完成后替换为普通 Node</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ReservationNode</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;&#125;</span><br><span class="line"><span class="comment">// 作为 treebin 的头节点, 存储 root 和 first</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TreeBin</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;&#125;</span><br><span class="line"><span class="comment">// 作为 treebin 的节点, 存储 parent, left, right</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">TreeNode</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">Node</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;&#125;</span><br></pre></td></tr></table></figure>
<h3 id="重要方法">重要方法</h3>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取 Node[] 中第 i 个 Node</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> &lt;K,V&gt; <span class="function">Node&lt;K,V&gt; <span class="title">tabAt</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="keyword">int</span> i)</span></span></span><br><span class="line"><span class="function"><span class="comment">// CAS 修改 Node[] 中第 i 个 Node 的值，c 为旧值，v 为新值</span></span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">final</span> &lt;K,V&gt; <span class="keyword">boolean</span> <span class="title">casTabAt</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="keyword">int</span> i, Node&lt;K,V&gt; c, Node&lt;K,V&gt; v)</span></span></span><br><span class="line"><span class="function"><span class="comment">// 直接修改 Node[] 中第 i 个 Node 的值，v 为新值</span></span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">final</span> &lt;K,V&gt; <span class="keyword">void</span> <span class="title">setTabAt</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="keyword">int</span> i, Node&lt;K，V&gt; v)</span></span></span><br></pre></td></tr></table></figure>
<h3 id="构造器分析">构造器分析</h3>
<p>可以看到实现了懒惰初始化，在构造方法中仅仅计算了 table 的大小，以后在第一次使用时才会真正创建</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// initialCapacity：初始容量，loadFactor：负载因子，concurrencyLevel：并发度</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ConcurrentHashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity, <span class="keyword">float</span> loadFactor, <span class="keyword">int</span> concurrencyLevel)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!(loadFactor &gt; <span class="number">0.0f</span>) || initialCapacity &lt; <span class="number">0</span> || concurrencyLevel &lt;= <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException();</span><br><span class="line">    <span class="comment">// 初始容量需要大于等于并发度</span></span><br><span class="line">    <span class="keyword">if</span> (initialCapacity &lt; concurrencyLevel)   <span class="comment">// Use at least as many bins</span></span><br><span class="line">        initialCapacity = concurrencyLevel;   <span class="comment">// as estimated threads</span></span><br><span class="line">    <span class="keyword">long</span> size = (<span class="keyword">long</span>)(<span class="number">1.0</span> + (<span class="keyword">long</span>)initialCapacity / loadFactor);</span><br><span class="line">    <span class="comment">// tableSizeFor 仍然是保证计算的大小是 2^n，即 16，32，64 ...</span></span><br><span class="line">    <span class="keyword">int</span> cap = (size &gt;= (<span class="keyword">long</span>)MAXIMUM_CAPACITY) ?</span><br><span class="line">        MAXIMUM_CAPACITY : tableSizeFor((<span class="keyword">int</span>)size);</span><br><span class="line">    <span class="keyword">this</span>.sizeCtl = cap;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="get流程">get流程</h3>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>&#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; e, p; <span class="keyword">int</span> n, eh; K ek;</span><br><span class="line">    <span class="comment">// spread 方法能确保返回结果是正数</span></span><br><span class="line">    <span class="keyword">int</span> h = spread(key.hashCode());</span><br><span class="line">    <span class="keyword">if</span> ((tab = table) != <span class="keyword">null</span> &amp;&amp; (n = tab.length) &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">        (e = tabAt(tab, (n - <span class="number">1</span>) &amp; h)) != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 如果头节点已经是要查找的 key</span></span><br><span class="line">        <span class="keyword">if</span> ((eh = e.hash) == h) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((ek = e.key) == key || (ek != <span class="keyword">null</span> &amp;&amp; key.equals(ek)))</span><br><span class="line">                <span class="keyword">return</span> e.val;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// hash 为负数表示该 bin 在扩容中或是 treebin，这时调用 find 方法来查找</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (eh &lt; <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">return</span> (p = e.find(h, key)) != <span class="keyword">null</span> ? p.val : <span class="keyword">null</span>;</span><br><span class="line">        <span class="comment">// 正常遍历链表，用 equals 比较</span></span><br><span class="line">        <span class="keyword">while</span> ((e = e.next) != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (e.hash == h &amp;&amp;</span><br><span class="line">                ((ek = e.key) == key || (ek != <span class="keyword">null</span> &amp;&amp; key.equals(ek))))</span><br><span class="line">                <span class="keyword">return</span> e.val;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="put流程">put流程</h3>
<p>以下数组简称（table），链表简称（bin）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> putVal(key, value, <span class="keyword">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// java.util.concurrent.ConcurrentHashMap#putVal</span></span><br><span class="line"><span class="comment">// onlyIfAbsent：false -&gt; 每次会用新值覆盖旧值</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> V <span class="title">putVal</span><span class="params">(K key, V value, <span class="keyword">boolean</span> onlyIfAbsent)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (key == <span class="keyword">null</span> || value == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">    <span class="comment">// 其中 spread 方法会综合高位低位，具有更好的 hash 特性，保证 hash 是正整数</span></span><br><span class="line">    <span class="keyword">int</span> hash = spread(key.hashCode());</span><br><span class="line">    <span class="keyword">int</span> binCount = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (Node&lt;K,V&gt;[] tab = table;;) &#123;</span><br><span class="line">        <span class="comment">// f 是链表头节点</span></span><br><span class="line">        <span class="comment">// fh 是链表头节点的 hash</span></span><br><span class="line">        <span class="comment">// i 是链表在 table 中的下标</span></span><br><span class="line">        Node&lt;K,V&gt; f; <span class="keyword">int</span> n, i, fh;</span><br><span class="line">        <span class="comment">// 要创建 table</span></span><br><span class="line">        <span class="keyword">if</span> (tab == <span class="keyword">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line">            <span class="comment">// 初始化 table 使用了 CAS，无需 synchronized。创建成功，进入下一轮循环</span></span><br><span class="line">            tab = initTable();</span><br><span class="line">        <span class="comment">// 要创建链表头节点</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(tab, i = (n - <span class="number">1</span>) &amp; hash)) == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 添加链表头使用了 CAS，无需 synchronized</span></span><br><span class="line">            <span class="keyword">if</span> (casTabAt(tab, i, <span class="keyword">null</span>,</span><br><span class="line">                         <span class="keyword">new</span> Node&lt;K,V&gt;(hash, key, value, <span class="keyword">null</span>)))</span><br><span class="line">                <span class="keyword">break</span>;                   <span class="comment">// no lock when adding to empty bin</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 帮忙扩容，（MOVED 的值是 -1）</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == MOVED)</span><br><span class="line">            <span class="comment">// 帮忙之后，进入下一轮循环</span></span><br><span class="line">            tab = helpTransfer(tab, f);</span><br><span class="line">		<span class="comment">// 能进入这个 else，说明当前既不是处在初始化过程中，也不是处在扩容过程中。</span></span><br><span class="line">        <span class="comment">// 桶下标冲突</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            V oldVal = <span class="keyword">null</span>;</span><br><span class="line">            <span class="comment">// 锁住链表头节点</span></span><br><span class="line">            <span class="keyword">synchronized</span> (f) &#123;</span><br><span class="line">                <span class="comment">// 再次确认链表头节点没有被移动</span></span><br><span class="line">                <span class="keyword">if</span> (tabAt(tab, i) == f) &#123;</span><br><span class="line">                    <span class="comment">// 链表</span></span><br><span class="line">                    <span class="keyword">if</span> (fh &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                        binCount = <span class="number">1</span>;</span><br><span class="line">                        <span class="comment">// 遍历链表</span></span><br><span class="line">                        <span class="keyword">for</span> (Node&lt;K,V&gt; e = f;; ++binCount) &#123;</span><br><span class="line">                            K ek;</span><br><span class="line">                            <span class="comment">// 找到相同的 key</span></span><br><span class="line">                            <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                                ((ek = e.key) == key ||</span><br><span class="line">                                 (ek != <span class="keyword">null</span> &amp;&amp; key.equals(ek)))) &#123;</span><br><span class="line">                                oldVal = e.val;</span><br><span class="line">                                <span class="comment">// 更新</span></span><br><span class="line">                                <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                    e.val = value;</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                            Node&lt;K,V&gt; pred = e;</span><br><span class="line">                            <span class="comment">// 已经是最后的节点了，新增 Node，追加至链表尾</span></span><br><span class="line">                            <span class="keyword">if</span> ((e = e.next) == <span class="keyword">null</span>) &#123;</span><br><span class="line">                                pred.next = <span class="keyword">new</span> Node&lt;K,V&gt;(hash, key,</span><br><span class="line">                                                          value, <span class="keyword">null</span>);</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// 红黑树</span></span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) &#123;</span><br><span class="line">                        Node&lt;K,V&gt; p;</span><br><span class="line">                        binCount = <span class="number">2</span>;</span><br><span class="line">                        <span class="comment">// putTreeVal 会看 key 是否已经在树中，是则返回对应的 TreeNode</span></span><br><span class="line">                        <span class="keyword">if</span> ((p = ((TreeBin&lt;K,V&gt;)f).putTreeVal(hash, key,</span><br><span class="line">                                                              value)) != <span class="keyword">null</span>) &#123;</span><br><span class="line">                            oldVal = p.val;</span><br><span class="line">                            <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                p.val = value;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;<span class="comment">// 释放链表头节点的锁</span></span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (binCount != <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 如果链表长度 &gt;= 树化阈值(8)，进行链表转换为红黑树</span></span><br><span class="line">                <span class="keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD)</span><br><span class="line">                    treeifyBin(tab, i);</span><br><span class="line">                <span class="keyword">if</span> (oldVal != <span class="keyword">null</span>)</span><br><span class="line">                    <span class="keyword">return</span> oldVal;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 增加 size 计数，(进行扩容)</span></span><br><span class="line">    addCount(<span class="number">1L</span>, binCount);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// java.util.concurrent.ConcurrentHashMap#initTable</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Node&lt;K,V&gt;[] initTable() &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; <span class="keyword">int</span> sc;</span><br><span class="line">    <span class="keyword">while</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((sc = sizeCtl) &lt; <span class="number">0</span>)</span><br><span class="line">            Thread.yield(); <span class="comment">// lost initialization race; just spin</span></span><br><span class="line">        <span class="comment">// 尝试将 sizeCtl 设置为 -1（表示初始化 table）</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, -<span class="number">1</span>)) &#123;</span><br><span class="line">            <span class="comment">// 获得锁，创建 table，这时其他线程会在 while() 循环中 yiled 直至 table 创建</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> ((tab = table) == <span class="keyword">null</span> || tab.length == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">int</span> n = (sc &gt; <span class="number">0</span>) ? sc : DEFAULT_CAPACITY;</span><br><span class="line">                    <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line">                    Node&lt;K,V&gt;[] nt = (Node&lt;K,V&gt;[])<span class="keyword">new</span> Node&lt;?,?&gt;[n];</span><br><span class="line">                    table = tab = nt;</span><br><span class="line">                    sc = n - (n &gt;&gt;&gt; <span class="number">2</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                sizeCtl = sc;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> tab;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// java.util.concurrent.ConcurrentHashMap#addCount</span></span><br><span class="line"><span class="comment">// check 是之前 binCount 的个数</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">addCount</span><span class="params">(<span class="keyword">long</span> x, <span class="keyword">int</span> check)</span> </span>&#123;</span><br><span class="line">    CounterCell[] as; <span class="keyword">long</span> b, s;</span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">        <span class="comment">// 已经有了 counterCelss，向 cell 累加</span></span><br><span class="line">        (as = counterCells) != <span class="keyword">null</span> ||</span><br><span class="line">        <span class="comment">// 还没有，向 baseCount 累加</span></span><br><span class="line">        !U.compareAndSwapLong(<span class="keyword">this</span>, BASECOUNT, b = baseCount, s = b + x)) &#123;</span><br><span class="line">        CounterCell a; <span class="keyword">long</span> v; <span class="keyword">int</span> m;</span><br><span class="line">        <span class="keyword">boolean</span> uncontended = <span class="keyword">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (</span><br><span class="line">            <span class="comment">// 还没有 counterCells</span></span><br><span class="line">            as == <span class="keyword">null</span> || (m = as.length - <span class="number">1</span>) &lt; <span class="number">0</span> ||</span><br><span class="line">            <span class="comment">// 还没有 cell</span></span><br><span class="line">            (a = as[ThreadLocalRandom.getProbe() &amp; m]) == <span class="keyword">null</span> ||</span><br><span class="line">            <span class="comment">// cell cas 增加计数失败</span></span><br><span class="line">            !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) &#123;</span><br><span class="line">            <span class="comment">// 创建累加单元数组和 cell，累加重试</span></span><br><span class="line">            fullAddCount(x, uncontended);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (check &lt;= <span class="number">1</span>)</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        <span class="comment">// 获取元素个数</span></span><br><span class="line">        s = sumCount();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (check &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        Node&lt;K,V&gt;[] tab, nt; <span class="keyword">int</span> n, sc;</span><br><span class="line">        <span class="keyword">while</span> (s &gt;= (<span class="keyword">long</span>)(sc = sizeCtl) &amp;&amp; (tab = table) != <span class="keyword">null</span> &amp;&amp;</span><br><span class="line">               (n = tab.length) &lt; MAXIMUM_CAPACITY) &#123;</span><br><span class="line">            <span class="keyword">int</span> rs = resizeStamp(n);</span><br><span class="line">            <span class="keyword">if</span> (sc &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> ((sc &gt;&gt;&gt; RESIZE_STAMP_SHIFT) != rs || sc == rs + <span class="number">1</span> ||</span><br><span class="line">                    sc == rs + MAX_RESIZERS || (nt = nextTable) == <span class="keyword">null</span> ||</span><br><span class="line">                    transferIndex &lt;= <span class="number">0</span>)</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="comment">// newtable 已经创建了，帮忙扩容</span></span><br><span class="line">                <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, sc + <span class="number">1</span>))</span><br><span class="line">                    transfer(tab, nt);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 需要扩容，这时 newtable 未创建</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc,</span><br><span class="line">                                         (rs &lt;&lt; RESIZE_STAMP_SHIFT) + <span class="number">2</span>))</span><br><span class="line">                transfer(tab, <span class="keyword">null</span>);</span><br><span class="line">            s = sumCount();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="size计算流程">size计算流程</h3>
<p>size 计算实际发生在 put，remove 改变集合元素的操作之中</p>
<ul>
<li>没有竞争发生，向 baseCount 累加计数</li>
<li>有竞争发生，新建 counterCells，向其中的一个 cell 累加计数
<ul>
<li>counterCells 初始化有两个 cell</li>
<li>如果计数竞争比较激烈，会创建新的 cell 来累加计数</li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> n = sumCount();</span><br><span class="line">    <span class="keyword">return</span> ((n &lt; <span class="number">0L</span>) ? <span class="number">0</span> :</span><br><span class="line">            (n &gt; (<span class="keyword">long</span>)Integer.MAX_VALUE) ? Integer.MAX_VALUE :</span><br><span class="line">            (<span class="keyword">int</span>)n);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">long</span> <span class="title">sumCount</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    CounterCell[] as = counterCells; CounterCell a;</span><br><span class="line">    <span class="comment">// 将 baseCount 计数与所有 cell 计数累加</span></span><br><span class="line">    <span class="keyword">long</span> sum = baseCount;</span><br><span class="line">    <span class="keyword">if</span> (as != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; as.length; ++i) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((a = as[i]) != <span class="keyword">null</span>)</span><br><span class="line">                sum += a.value;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> sum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

                
                <hr>
                <!-- Pager -->
                <ul class="pager">
                    
                        <li class="previous">
                            <a href="/article/JVM/" data-toggle="tooltip" data-placement="top" title="JVM—内存结构、垃圾回收、类加载与字节码技术">&larr; 上一篇</a>
                        </li>
                    
                    
                        <li class="next">
                            <a href="/article/多线程—CAS无锁-乐观锁/" data-toggle="tooltip" data-placement="top" title="多线程—乐观锁CAS机制">下一篇 &rarr;</a>
                        </li>
                    
                </ul>

                <br>

                <!--打赏-->
                
                <!--打赏-->

                <br>
                <!--分享-->
                
                <!--分享-->
                <br>                       
                
                <!-- require APlayer -->
                
            </div>
            
            <!-- Tabe of Content -->
            <!-- Table of Contents -->

  
    <style>
      span.toc-nav-number{
        display: none
      }
    </style>
  
    
      <aside id="sidebar">
        <div id="toc" class="toc-article">
        <strong class="toc-title">目录</strong>
        
          <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#aqs概述"><span class="toc-nav-number">1.</span> <span class="toc-nav-text">AQS&#x6982;&#x8FF0;</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#实现不可重入锁"><span class="toc-nav-number">2.</span> <span class="toc-nav-text">&#x5B9E;&#x73B0;&#x4E0D;&#x53EF;&#x91CD;&#x5165;&#x9501;</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#reentrantlock原理"><span class="toc-nav-number">3.</span> <span class="toc-nav-text">ReentrantLock&#x539F;&#x7406;</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#reentrantlock特性"><span class="toc-nav-number">3.1.</span> <span class="toc-nav-text">ReentrantLock&#x7279;&#x6027;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#reentrantlock非公平锁实现原理"><span class="toc-nav-number">3.2.</span> <span class="toc-nav-text">ReentrantLock&#x975E;&#x516C;&#x5E73;&#x9501;&#x5B9E;&#x73B0;&#x539F;&#x7406;</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#加锁流程"><span class="toc-nav-number">3.2.1.</span> <span class="toc-nav-text"><strong>&#x52A0;&#x9501;&#x6D41;&#x7A0B;</strong></span></a></li></ol></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#解锁流程"><span class="toc-nav-number">3.3.</span> <span class="toc-nav-text">&#x89E3;&#x9501;&#x6D41;&#x7A0B;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#锁重入原理"><span class="toc-nav-number">3.4.</span> <span class="toc-nav-text">&#x9501;&#x91CD;&#x5165;&#x539F;&#x7406;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#公平锁和非公平锁实现原理的区别"><span class="toc-nav-number">3.5.</span> <span class="toc-nav-text">&#x516C;&#x5E73;&#x9501;&#x548C;&#x975E;&#x516C;&#x5E73;&#x9501;&#x5B9E;&#x73B0;&#x539F;&#x7406;&#x7684;&#x533A;&#x522B;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#中断恢复后的执行流程"><span class="toc-nav-number">3.6.</span> <span class="toc-nav-text">&#x4E2D;&#x65AD;&#x6062;&#x590D;&#x540E;&#x7684;&#x6267;&#x884C;&#x6D41;&#x7A0B;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#可打断模式"><span class="toc-nav-number">3.7.</span> <span class="toc-nav-text">&#x53EF;&#x6253;&#x65AD;&#x6A21;&#x5F0F;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#条件变量实现原理"><span class="toc-nav-number">3.8.</span> <span class="toc-nav-text">&#x6761;&#x4EF6;&#x53D8;&#x91CF;&#x5B9E;&#x73B0;&#x539F;&#x7406;</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#await"><span class="toc-nav-number">3.8.1.</span> <span class="toc-nav-text">await</span></a></li><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#signal"><span class="toc-nav-number">3.8.2.</span> <span class="toc-nav-text">signal</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#读写锁"><span class="toc-nav-number">4.</span> <span class="toc-nav-text">&#x8BFB;&#x5199;&#x9501;</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#reentrantreadwritelock"><span class="toc-nav-number">4.1.</span> <span class="toc-nav-text">ReentrantReadWriteLock</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#stampedlock"><span class="toc-nav-number">4.2.</span> <span class="toc-nav-text">StampedLock</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#semaphore"><span class="toc-nav-number">5.</span> <span class="toc-nav-text">Semaphore</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#countdownlatch"><span class="toc-nav-number">6.</span> <span class="toc-nav-text">CountDownLatch</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#cyclicbarrier"><span class="toc-nav-number">7.</span> <span class="toc-nav-text">CyclicBarrier</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#线程安全集合类"><span class="toc-nav-number">8.</span> <span class="toc-nav-text">&#x7EBF;&#x7A0B;&#x5B89;&#x5168;&#x96C6;&#x5408;&#x7C7B;</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#concurrenthashmap"><span class="toc-nav-number">9.</span> <span class="toc-nav-text">ConcurrentHashMap</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#如何保证线程安全"><span class="toc-nav-number">9.1.</span> <span class="toc-nav-text">&#x5982;&#x4F55;&#x4FDD;&#x8BC1;&#x7EBF;&#x7A0B;&#x5B89;&#x5168;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#高效扩容"><span class="toc-nav-number">9.2.</span> <span class="toc-nav-text">&#x9AD8;&#x6548;&#x6269;&#x5BB9;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#concurrenthashmap的put方法是如何通过cas确保线程安全的"><span class="toc-nav-number">9.3.</span> <span class="toc-nav-text">ConcurrentHashMap&#x7684;put&#x65B9;&#x6CD5;&#x662F;&#x5982;&#x4F55;&#x901A;&#x8FC7;CAS&#x786E;&#x4FDD;&#x7EBF;&#x7A0B;&#x5B89;&#x5168;&#x7684;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#重要属性和内部类"><span class="toc-nav-number">9.4.</span> <span class="toc-nav-text">&#x91CD;&#x8981;&#x5C5E;&#x6027;&#x548C;&#x5185;&#x90E8;&#x7C7B;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#重要方法"><span class="toc-nav-number">9.5.</span> <span class="toc-nav-text">&#x91CD;&#x8981;&#x65B9;&#x6CD5;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#构造器分析"><span class="toc-nav-number">9.6.</span> <span class="toc-nav-text">&#x6784;&#x9020;&#x5668;&#x5206;&#x6790;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#get流程"><span class="toc-nav-number">9.7.</span> <span class="toc-nav-text">get&#x6D41;&#x7A0B;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#put流程"><span class="toc-nav-number">9.8.</span> <span class="toc-nav-text">put&#x6D41;&#x7A0B;</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#size计算流程"><span class="toc-nav-number">9.9.</span> <span class="toc-nav-text">size&#x8BA1;&#x7B97;&#x6D41;&#x7A0B;</span></a></li></ol></li></ol>
        
        </div>
      </aside>
    

                
            <!-- Sidebar Container -->
            <div class="
                col-lg-8 col-lg-offset-2
                col-md-10 col-md-offset-1
                sidebar-container">

                <!-- Featured Tags -->
                
                <section>
                    <!-- no hr -->
                    <h5><a href="/tags/">标签</a></h5>
                    <div class="tags">
                       
                          <a class="tag" href="/tags/#java" title="java">java</a>
                        
                    </div>
                </section>
                

                <!-- Friends Blog -->
				<!--
                
                <hr>
                <h5>朋友们</h5>
                <ul class="list-inline">

                    
                        <li><a href="https://tyzhang.top/" target="_blank">ztygalaxy</a></li>
                    
                        <li><a href="http://www.yctang.club/" target="_blank">yctang</a></li>
                    
                        <li><a href="https://dolnw.github.io" target="_blank">JCWang</a></li>
                    
                </ul>
                
				-->
            </div>
        </div>
    </div>
</article>


<!-- async load function -->
<script>
    function async(u, c) {
      var d = document, t = 'script',
          o = d.createElement(t),
          s = d.getElementsByTagName(t)[0];
      o.src = u;
      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
      s.parentNode.insertBefore(o, s);
    }
</script>
<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->
<script>
    async("https://cdn.bootcss.com/anchor-js/1.1.1/anchor.min.js",function(){
        anchors.options = {
          visible: 'hover',
          placement: 'left',
          icon: ''
        };
        anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');
    })
</script>
<style>
    /* place left on bigger screen */
    @media all and (min-width: 800px) {
        .anchorjs-link{
            position: absolute;
            left: -0.75em;
            font-size: 1.1em;
            margin-top : -0.1em;
        }
    }
</style>


<!-- chrome Firefox 中文锚点定位失效-->
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<!-- smooth scroll behavior polyfill  -->
<script type="text/javascript" src="/js/smoothscroll.js"></script>
<script>
        $('#toc').on('click','a',function(a){
            // var isChrome = window.navigator.userAgent.indexOf("Chrome") !== -1;
            // console.log(window.navigator.userAgent,isChrome)
                // if(isChrome) {
                    // console.log(a.currentTarget.outerHTML);
                    // console.log($(a.currentTarget).attr("href"));
                    //跳转到指定锚点
                    // document.getElementById(a.target.innerText.toLowerCase()).scrollIntoView(true);
                    document.getElementById($(a.currentTarget).attr("href").replace("#","")).scrollIntoView({behavior: 'smooth' });
                // }
        })  
</script>


    <!-- Footer -->
    <!-- Footer -->
<footer>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                <ul class="list-inline text-center">
                
                
                
				
                

                

                
                    <li>
                        <a target="_blank"  href="https://github.com/songzblink">
                            <span class="fa-stack fa-lg">
                                <i class="fa fa-circle fa-stack-2x"></i>
                                <i class="fa fa-github fa-stack-1x fa-inverse"></i>
                            </span>
                        </a>
                    </li>
                

                

                </ul>
                <p class="copyright text-muted">
                    Copyright &copy; 宋正兵 2021 
                    <br>
                    Theme by <a href="http://www.huweihuang.com">Huwei Huang</a>
                    re-Ported by <a href="https://github.com/ztygalaxy">ztygalaxy</a>
					
                </p>
            </div>
        </div>
    </div>
</footer>

<!-- jQuery -->
<script src="/js/jquery.min.js"></script>

<!-- Bootstrap Core JavaScript -->
<script src="/js/bootstrap.min.js"></script>

<!-- Custom Theme JavaScript -->
<script src="/js/hux-blog.min.js"></script>


<!-- async load function -->
<script>
    function async(u, c) {
      var d = document, t = 'script',
          o = d.createElement(t),
          s = d.getElementsByTagName(t)[0];
      o.src = u;
      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
      s.parentNode.insertBefore(o, s);
    }
</script>

<!-- 
     Because of the native support for backtick-style fenced code blocks 
     right within the Markdown is landed in Github Pages, 
     From V1.6, There is no need for Highlight.js, 
     so Huxblog drops it officially.

     - https://github.com/blog/2100-github-pages-now-faster-and-simpler-with-jekyll-3-0  
     - https://help.github.com/articles/creating-and-highlighting-code-blocks/    
-->
<!--
    <script>
        async("http://cdn.bootcss.com/highlight.js/8.6/highlight.min.js", function(){
            hljs.initHighlightingOnLoad();
        })
    </script>
    <link href="http://cdn.bootcss.com/highlight.js/8.6/styles/github.min.css" rel="stylesheet">
-->


<!-- jquery.tagcloud.js -->
<script>
    // only load tagcloud.js in tag.html
    if($('#tag_cloud').length !== 0){
        async("zbsong.top/js/jquery.tagcloud.js",function(){
            $.fn.tagcloud.defaults = {
                //size: {start: 1, end: 1, unit: 'em'},
                color: {start: '#bbbbee', end: '#0085a1'},
            };
            $('#tag_cloud a').tagcloud();
        })
    }
</script>

<!--fastClick.js -->
<script>
    async("https://cdn.bootcss.com/fastclick/1.0.6/fastclick.min.js", function(){
        var $nav = document.querySelector("nav");
        if($nav) FastClick.attach($nav);
    })
</script>


<!-- Google Analytics -->




<!-- Baidu Tongji -->






	<a id="rocket" href="#top" class=""></a>
	<script type="text/javascript" src="/js/totop.js?v=1.0.0" async=""></script>
    <script type="text/javascript" src="/js/toc.js?v=1.0.0" async=""></script>
<!-- Image to hack wechat -->
<img src="zbsong.top/img/icon_wechat.png" width="0" height="0" />
<!-- Migrate from head to bottom, no longer block render and still work -->

<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
        tex2jax: {
            inlineMath: [ ["$","$"], ["\\(","\\)"] ],
            skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
            processEscapes: true
        }
    });
    MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax();
        for (var i = 0; i < all.length; ++i)
            all[i].SourceElement().parentNode.className += ' has-jax';
    });
</script>
<!--
<script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script>
</body>

</html>
